begin
  # In case you use Gosu via rubygems.
  require 'rubygems'
rescue LoadError
  # In case you don't.
end

require 'gosu'
include Gosu

require 'yaml'

require "unit"
require "missile"
require "map"
require "menu"
require "scoreboard"

def angle_to_radians(angle)
  angle / 360.to_f * 3.14159262 * 2
end

def rgb_to_color(r, g, b)
  col = 0xff000000
  col += [r, 255].min * 256 ** 2
  col += [g, 255].min * 256
  col += [b, 255].min
end

module ZOrder
  Sky, Map, MapEffects, Items, Units, Weapons, Missiles, Particles, HUD, BlackFade, Menu = (0..10).to_a
end

class Game < Window
  attr_reader :map, :update_time, :colors, :skin_colors, :game_types, :screen_widths, :screen_heights, :team_scores, :teams, :winning_team, :game_started
  attr_accessor :object_space, :units, :missiles, :options, :menu, :controllers
  GRAVITY = 0.3
  RESTART_TIME = 100
  MAX_PLAYERS = 8
  
  def initialize(w, h, f)
    super(w, h, f)
    self.caption = "Rock, Paper, Grenade"
    init_images
    init_keyboard_constants
    init_arrays
    @game_started = false
    @menu = Menu.new(self)
    show_menu
  end
  
  def show_menu
    @mode = :menu
    @black_fade = 192
    @menu.go_to_main_menu
  end
  
  def start_game
    @menu.add_main_option(ResumeGameOption.new) unless @game_started
    @game_started = true
    @mode = :game
    @scoreboard = Scoreboard.new(self)
    @units = []
    @team_scores = []
    MAX_PLAYERS.times do |n|
      if GameConfig["unit#{n}_controller"] != "None"
        @units[n] = Unit.new(self, GameConfig["unit#{n}_uniform_color"], GameConfig["unit#{n}_skin_color"], GameConfig["unit#{n}_team"], n, GameConfig["unit#{n}_controller"])
        @human_right = @units[n] if GameConfig["unit#{n}_controller"] == "Human (right)"
        @human_left = @units[n] if GameConfig["unit#{n}_controller"] == "Human (left)"
        @team_scores[GameConfig["unit#{n}_team"]] = 0
      end
    end
    @update_time = 0
    restart
  end
  
  def restart
    @black_fade = 255
    @restart_timer = -1
    @object_space = []
    @map = nil
    GC.start
    @map = Map.new(self, self.width+200, height)
    @font = Gosu::Font.new(self, Gosu::default_font_name, 20)
    @start_time = Time.now
    @missiles = []
    
    num_players = 0
    @units.each do |u|
      num_players += 1 if u != nil
    end
    
    player_i = 0
    @units.each do |u|
      player_i += 1 if u != nil
      xpos = self.width * (player_i) / (num_players + 1)
      u.place(@map, xpos.to_i, self.height - @map[xpos] + 30, :right)
    end
  end
  
  def resume_game
    @mode = :game
    @units[0].update_colors(GameConfig["unit0_uniform_color"], GameConfig["unit0_skin_color"])
    @units[1].update_colors(GameConfig["unit1_uniform_color"], GameConfig["unit1_skin_color"])
  end
  
  def game_over(winning_team)
    @winning_team = winning_team
    @mode = :menu
    @black_fade = 192
    @menu.go_to_game_over
  end
  
  def end_game
    @game_started = false
    @menu.remove_main_option(ResumeGameOption)
  end
  
  def do_out(s)
    @out = s
  end
  
  def draw
    if @game_started
      @map.draw
      @units.each {|t| t.draw}
      @missiles.each { |m| m.draw }
      @object_space.each { |o| o.draw }
      @scoreboard.draw
    end
    
    if @black_fade > 10
      c = Gosu::Color.new(@black_fade.to_i, 0, 0, 0)
      draw_quad(0, 0, c, width, 0, c, 0, height, c, width, height, c, ZOrder::BlackFade)
    end
    if @mode == :menu
      @menu.draw
    end
  end
  
  def update
    if @mode == :menu
      @menu.update
    elsif @mode == :game
      if @last_sec != Time.now.sec
        @update_time = Time.now - @prev_time unless @prev_time == nil
        @last_sec = Time.now.sec
      end
      @prev_time = Time.now
      
      @restart_timer -= 1 if @restart_timer > -1 and @missiles.find { |m| m.dies } == nil
      if @restart_timer == 0
        if teams_alive.size == 1
          @team_scores[teams_alive[0]] += 1 
          if @team_scores[teams_alive[0]] == GameConfig["point_limit"]
            game_over(teams_alive[0])
          else
            restart
          end
        else
          restart
        end
      end
      if @black_fade > 1
        @black_fade -= 5
      end
      move_x = weapon_angle_adjust = force_adjust = 0
      if @human_left
        fire_button_down = false
        move_x = -1 if button_down? Button::KbA
        move_x = 1 if button_down? Button::KbD
        weapon_angle_adjust = 1 if button_down? Button::KbW
        weapon_angle_adjust = -1 if button_down? Button::KbS
        @human_left.update(move_x, weapon_angle_adjust, fire_button_down)
      end
      
      if @human_right
        move_x = weapon_angle_adjust = force_adjust = 0
        fire_button_down = false
        move_x = -1 if button_down? Button::KbNumpad4
        move_x = 1 if button_down? Button::KbNumpad6
        weapon_angle_adjust = 1 if button_down? Button::KbNumpad8
        weapon_angle_adjust = -1 if button_down? Button::KbNumpad5
        @human_right.update(move_x, weapon_angle_adjust, fire_button_down)
      end
      
      @units.each do |u|
        u.ai_update unless u.human?
      end
      
      @missiles.each { |m| m.update }
      @object_space.reject! do |obj|
        !obj.update
      end
    end
  end
  def button_down(id)
    if @mode == :game
      case id
        when Button::KbF9
          @map.toggle_display
        when Button::KbF5
          restart
        when Button::KbReturn, Button::KbEscape
          show_menu
      end
      
      if @human_left
        @human_left.switch_weapon(1) if id == Button::KbE
        @human_left.switch_weapon(-1) if id == Button::KbQ
        @human_left.fire if [Button::KbSpace, Button::KbLeftControl].include?(id)
      end
      if @human_right
        @human_right.switch_weapon(1) if id == Button::KbNumpad9
        @human_right.switch_weapon(-1) if id == Button::KbNumpad7
        @human_right.fire if [Button::KbNumpad0, Button::KbEnter].include?(id)
      end
    elsif @mode == :menu
      case id
        when Button::KbDown
          @menu.change_selection(1)
        when Button::KbUp
          @menu.change_selection(-1)
        when Button::KbRight, Button::KbReturn
          @menu.handle_selection(1)
        when Button::KbLeft
          @menu.handle_selection(-1)
        when Button::KbEscape
          @menu.handle_escape
        when Button::KbF6
          while true
            inp = gets.strip
            break if inp == "resume"
            eval inp
          end
      end
    end
  end
  
  def elapsed
    "%0.2d:%0.2d" % [(Time.now - @start_time) / 60, (Time.now - @start_time) % 60]
  end
  
  def missile_die(missile)
    missile_splash_hit(missile)
    m = @missiles.index(missile)
    @missiles.delete_at(m) if m
  end
  
  def missile_splash_hit(missile)
    x, y = missile.x, missile.y
    @units.each_with_index do |unit, i|
      unit_distances = [distance(x, y, unit.x, unit.y).abs]
      unit_distances << distance(x, y, unit.x - unit.width/2, unit.y - unit.height/2).abs
      unit_distances << distance(x, y, unit.x + unit.width/2, unit.y - unit.height/2).abs
      unit_distances << distance(x, y, unit.x - unit.width/2, unit.y + unit.height/2).abs
      unit_distances << distance(x, y, unit.x + unit.width/2, unit.y + unit.height/2).abs
      unit_distance = unit_distances.min
      unit_distance = 1 if unit_distance < 1
      if unit_distance < missile.splash_radius
        damage = missile.damage * (missile.splash_radius - unit_distance) / missile.splash_radius
        damage = 1 if damage > 0 and damage < 1
        unit.take_damage(missile.unit, damage, 0, 0, missile.poison)
      end
    end
  end
  
  def missile_disappear(missile)
    m = @missiles.index(missile)
    @missiles.delete_at(m) if m
  end
  
  def hit?(missile, x, y)
    unit_hit = nil
    @units.each_with_index do |a, i|
      if x > a.x - a.width / 2 && x < a.x + a.width / 2
        if y > a.y - a.height / 2 && y < a.y + a.height / 2
          #only hit dead units again if the missile does damage; this prevents the dropped weapon getting stuck in the air
          if !a.gibbed and (!a.dead or missile.damage > 0) and (missile.lethality_time == nil or missile.lifetime >= missile.lethality_time or a != missile.unit)
            unit_hit = i
            if missile.splash_radius == 0 and missile.damage > 0
              a.take_damage(missile.unit, missile.damage, x, y, missile.poison)
            end
            missile.unit.notify_hit(a)
            DebugOut.out "#{missile.class.to_s} hit #{unit_hit}"
          end
        end
      end
    end
    unit_hit
  end
  
  def add_debris(x, y, angle, color, speed)
    Debris.new(self, x, y, angle, color, speed)
  end
  
  def unit_died
    @restart_timer = RESTART_TIME if teams_alive.size < 2
  end
  
  def teams_alive
    teams_alive = []
    @units.each do |u|
      teams_alive.push u.team unless u.dead
    end
    teams_alive.uniq
  end
  
  def teams
    ret = []
    MAX_PLAYERS.times { |n| ret << GameConfig["unit#{n}_team"] if GameConfig["unit#{n}_controller"] != "None" }
    ret.uniq
  end
  
  def play(sound, sound_length = 1)
    if sound.methods.include?("play")
      sound.play(GameConfig["sound_volume"]/10.0, sound_length)
    else
      Sounds[sound].play(GameConfig["sound_volume"]/10.0, sound_length)
    end
  end
  
  def init_keyboard_constants
    unless defined?(Button::KbA)
      ('a'..'z').each do |letter|
        eval "Button::Kb#{letter.upcase} = #{self.char_to_button_id(letter)}"
      end
    end
  end
  
  def init_images
    Anims.add(self, "media/unit_skin.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/unit_uniform.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/unit_damage1.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/unit_damage2.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/unit_damage3.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/unit_damage4.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/unit_damage5.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/unit_damage6.png", Unit::DEFAULT_WIDTH, Unit::DEFAULT_HEIGHT)
    Anims.add(self, "media/fireball.png", 32, 32, true)
    Anims.add(self, "media/smoke.png", 32, 32, true)
  end
  
  def init_arrays
    @colors = [ 0xff7f0000,
      0xff007f0e,
      0xff004a7f,
      0xffeed800,
      0xffff6500,
      0xff656565,
      0xff57007f,
      0xffff0000,
      0xff00cc00,
      0xff0094ff]
    @skin_colors = [ 0xff888877 ]
    @game_types = ["Deathmatch"] ###
    @screen_widths = [640, 800, 1024, 1280, 1600, 2048, 2560]
    @screen_heights = [480, 600, 768, 960, 1024]
    @controllers = ["None", "Human (left)", "Human (right)", "Computer"]
  end
  
  def taken_controllers
    ret = []
    MAX_PLAYERS.times do |i|
      if ["Human (right)", "Human (left)"].include?(GameConfig["unit#{i}_controller"])
        ret << GameConfig["unit#{i}_controller"]
      end
    end
    ret
  end
  
  def taken_colors
    ret = []
    MAX_PLAYERS.times do |i|
      if GameConfig["unit#{i}_controller"] != "none"
        ret << GameConfig["unit#{i}_uniform_color"]
      end
    end
    ret
  end
end